//
//  FetchIPAdressManager.m
//  IPDirectConnection
//
//  Created by lianglibao on 2019/4/20.
//  Copyright © 2019 lianglibao. All rights reserved.
//

#import "FetchIPAdressManager.h"
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <net/if.h>
#include <netdb.h>

#define IOS_CELLULAR    @"pdp_ip0"
#define IOS_WIFI        @"en0"
#define IOS_VPN         @"utun0"
#define IP_ADDR_IPv4    @"ipv4"
#define IP_ADDR_IPv6    @"ipv6"

static NSMutableDictionary *__DNS_IPMap;
static NSMutableDictionary *__IP_DNSMap;
static dispatch_semaphore_t fetchIPSemaphore;

@implementation IPRegularModel

@end

@implementation FetchIPAdressManager

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __DNS_IPMap = [NSMutableDictionary dictionaryWithCapacity:1];
		__IP_DNSMap = [NSMutableDictionary dictionaryWithCapacity:1];
		fetchIPSemaphore = dispatch_semaphore_create(1);
    });
}

// 校验host是否是ip地址.
+ (IPRegularModel *)isIPadress:(NSString *)host {
    /*
     IPv4:唯一标准格式 -> 0-255.0-255.0-255.0-255
     IPv6: 标  准  格  式 -> abcd:abcd:abcd:abcd:abcd:abcd:abcd:abcd
     IPv6  压  缩  格  式 -> abcd::abcd:abcd:abcd:abcd
                               ::abcd:abcd:abcd
                               abcd:abcd:abcd:abcd:abcd::
                               ::1
                               ::
     IPv6压缩规则：必需至少两个全0块才可以压缩,且每个IPv6地址只能压缩一次,存在多个可以压缩的位置优先压缩左边的全0块
     */
    if (host.length) {
        // IPV4正则
        NSString *urlRegEx = @"^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$";
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", urlRegEx];
        BOOL isOK = [predicate evaluateWithObject:host];
        IPRegularModel *model = [[IPRegularModel alloc] init];
        if (!isOK) {
            // 开始验证是否为IPV6
             NSInteger count = [host componentsSeparatedByString:@":"].count;
            if ((count == 0) || (count > 8)) {// 0 个: 或 大于7个 : 直接返回NO!
                return nil;
            }
            
            // 功能:标准的IPV6地址(IPV6_COMPRESS_REGEX)
            // abcd:abcd:abcd:abcd:abcd:abcd:abcd:abcd
            urlRegEx = @"(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$";
            predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", urlRegEx];
            if ([predicate evaluateWithObject:host]) {
                model.isIPV6 = YES;
                model.isIP = YES;
                return model;
            }
            
            // 判断是否为压缩的IPV6地址
            if (count == 8) {
                /*由于IPv6压缩规则是必须要大于等于2个全0块才能压缩
                 不合法压缩 ： fe80:0000:8030:49ec:1fc6:57fa:ab52:fe69
                 ->           fe80::8030:49ec:1fc6:57fa:ab52:fe69
                 该不合法压缩地址直接压缩了处于第二个块的单独的一个全0块，
                 上述不合法地址不能通过一般情况的压缩正则表达式IPV6_COMPRESS_REGEX判断出其不合法
                 所以定义了如下专用于判断边界特殊压缩的正则表达式
                 (边界特殊压缩：开头或末尾为两个全0块，该压缩由于处于边界，且只压缩了2个全0块，不会导致':'数量变少)*/
                // 功能：抽取特殊的边界压缩情况
                urlRegEx = @"^(::(?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5})|((?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5}::)$";
            } else {
                // 功能：判断一般情况压缩的IPv6正则表达式
                urlRegEx = @"^((?:[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})*)?)::((?:([0-9A-Fa-f]{1,4}:)*[0-9A-Fa-f]{1,4})?)$";
            }
            predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", urlRegEx];
            isOK = [predicate evaluateWithObject:host];
            if ([predicate evaluateWithObject:host]) {
                model.isIPV6 = YES;
                model.isIP = YES;
                return model;
            }
            return nil;
        } else {
            model.isIP = YES;
            model.isIPV4 = YES;
            return model;
        }
    }
    return nil;
}

// 根据url获取ip地址.
+ (NSString *)fetchIPAdressWithURL:(NSURL *)url {
    if (![url.scheme isEqualToString:@"http"] && ![url.scheme isEqualToString:@"https"]) {
        return nil;
    }
    
    NSString *host = url.host;
    
    if (!url || !host) {
        return nil;
    }
    
    // 首先判断是否是IPV6地址
    if ([host containsString:@"["] && [host containsString:@"]"]) {
        host = [host stringByReplacingOccurrencesOfString:@"[" withString:@""];
        host = [host stringByReplacingOccurrencesOfString:@"]" withString:@""];
    }
    
    if ([self isIPadress:host]) {
        return [url absoluteString];
    }
    
    // 获取IP地址
    NSString *ip = [self fetchIPadressStringWithHost:host];
    if (!ip) {
        ip = [__DNS_IPMap objectForKey:host];
    }
    
    // 根据IP类型分别替换host策略
	NSString *ipAdress = url.absoluteString;
    if (ip) {
        IPRegularModel *model = [self isIPadress:host];
        if (model.isIPV6) {
            ip = [NSString stringWithFormat:@"[%@]", ip];
        }
		
		// 保存IP<->DNS映射
		dispatch_semaphore_wait(fetchIPSemaphore, DISPATCH_TIME_FOREVER);
        [__DNS_IPMap setObject:ip forKey:host];
		[__IP_DNSMap setValue:url.host forKey:ip];
		dispatch_semaphore_signal(fetchIPSemaphore);
		
        ipAdress = [ipAdress stringByReplacingOccurrencesOfString:url.host withString:ip];
    }
    
    return ipAdress;
}

+ (NSString *)getHostWithIPAdress:(NSString *)ip {
	return [__IP_DNSMap objectForKey:ip];
}

// MARK: - private methods
+ (NSString *)fetchIPadressStringWithHost:(NSString *)host {
    Boolean result,bResolved;
    CFHostRef hostRef;
    CFArrayRef addresses = NULL;
    NSMutableArray * ipsArr = [[NSMutableArray alloc] init];
    CFStringRef hostNameRef = CFStringCreateWithCString(kCFAllocatorDefault, [host cStringUsingEncoding:NSUTF8StringEncoding], kCFStringEncodingASCII);
    
    hostRef = CFHostCreateWithName(kCFAllocatorDefault, hostNameRef);
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    result = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL);
    if (result == TRUE) {
        addresses = CFHostGetAddressing(hostRef, &result);
    }
    bResolved = result == TRUE ? true : false;
    if (bResolved) {
        struct sockaddr_in* remoteAddr;
        for (int i = 0; i < CFArrayGetCount(addresses); i++) {
            CFDataRef saData = (CFDataRef)CFArrayGetValueAtIndex(addresses, i);
            remoteAddr = (struct sockaddr_in*)CFDataGetBytePtr(saData);
            
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
            if (remoteAddr && (remoteAddr->sin_family==AF_INET || remoteAddr->sin_family==AF_INET6)) {
                NSString *ipStr;
                if(remoteAddr->sin_family == AF_INET) {
                    if (inet_ntop(AF_INET, &remoteAddr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
                        ipStr = [NSString stringWithCString:addrBuf encoding:NSUTF8StringEncoding];
                    }
                } else {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)remoteAddr;
                    if (inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
                        ipStr = [NSString stringWithCString:addrBuf encoding:NSUTF8StringEncoding];
                    }
                }
                if (ipStr.length) {
                    [ipsArr addObject:ipStr];
                }
            }
            
//            // const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)
//            if (remoteAddr != NULL) {
//                //获取IP地址
//                char ip[16];
//                strcpy(ip, inet_ntoa(remoteAddr->sin_addr));
//                NSString * ipStr = [NSString stringWithCString:ip encoding:NSUTF8StringEncoding];
//                [ipsArr addObject:ipStr];
//            }
        }
    }
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    NSLog(@"33333 === ip === %@ === time cost: %0.3fs", ipsArr, end - start);
    CFRelease(hostNameRef);
    CFRelease(hostRef);
    return ipsArr.firstObject ? : nil;
}


// MARK: - 获取设备当前网络IP地址
/**
 获取设备当前网络IP地址

 @param preferIPv4 YES 输出ipv4地址, NO 输出ipv6地址
 @return 设备当前网络IP地址
 */
+ (NSString *)getIPAddress:(BOOL)preferIPv4 {
    NSArray *searchArray = preferIPv4 ?
    @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
    @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
    
    NSDictionary *addresses = [self getCurrentDeviceIPAddresses];
    NSLog(@"addresses: %@", addresses);
    
    __block NSString *address;
    [searchArray enumerateObjectsUsingBlock:^(NSString *key,
                                              NSUInteger idx,
                                              BOOL *stop) {
        address = addresses[key];
        // 筛选出IP地址格式
        if([self isIPadress:address]) *stop = YES;
     }];
    return address ? address : @"0.0.0.0";
}

+ (NSDictionary *)getCurrentDeviceIPAddresses {
    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
    // retrieve the current interfaces - returns 0 on success
    struct ifaddrs *interfaces;
    if(!getifaddrs(&interfaces)) {
        // Loop through linked list of interfaces
        struct ifaddrs *interface;
        for(interface=interfaces; interface; interface=interface->ifa_next) {
            if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
                continue; // deeply nested code harder to read
            }
            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
            if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
                NSString *type;
                if(addr->sin_family == AF_INET) {
                    if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv4;
                    }
                } else {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv6;
                    }
                }
                if(type) {
                    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
                    addresses[key] = [NSString stringWithUTF8String:addrBuf];
                }
            }
        }
        // Free memory
        freeifaddrs(interfaces);
    }
    return [addresses count] ? addresses : nil;
}

@end
